package br.unicamp.mc715.bank;

import java.io.Serializable;
import java.util.ArrayDeque;
import java.util.concurrent.Semaphore;

import org.apache.log4j.Logger;

/**
 * This class implements a very simple but particular bank account.
 * The particularity is the fact that it can never go into negative balances.
 * Initially the balance is 0.
 */
public class BankAccount extends Thread implements Serializable {

	private static final long serialVersionUID = 12350937586547926L;

	/**Logger utilizado por esta conta*/
	private transient Logger logger;
	/**Seção crítica para que apenas uma operação seja executada por vez.*/
	public transient Semaphore sem = new Semaphore(1);
	/**Armazena o "log" das operações que já foram executadas nesta conta.*/
	private ArrayDeque<BankOperation> log;
	/**ID desta conta*/
	private int accountID;
	/**Saldo atual desta conta*/
	private int balance;
	
	/**Construtor necessário para que a classe seja serializável*/
	@SuppressWarnings("unused")
	private BankAccount() {
		this(-1);
	}
	
	/**Cria uma nova conta, com o ID especificado
	 * @param accountID ID desta conta*/
	public BankAccount(int accountID) {
		this.accountID = accountID;
		this.balance = 0;
		this.log = new ArrayDeque<BankOperation>();
		this.logger = Logger.getLogger("Account #" + accountID);
	}
	
	/**
	 * Executa a operação especificada nesta conta.
	 * @param operation Operação que deve ser executada.
	 * @return o {@link #balance saldo} da conta após a execução desta operação
	 */
	public int execOp(BankOperation operation)
	{
		int b = -1;
	
		if(operation.getOperationType() == 'd')
		{
			deposit(operation.getOperationAmount());
		}
		else if(operation.getOperationType() == 'w')
		{
			withdraw(operation.getOperationAmount());
		}
		else if(operation.getOperationType() == 'b')
		{
			b = getBalance();
		}
		else
		{
			logger.error("Operation '" + operation.getOperationType() + "' not allowed!");
		}
			
		log.offer(operation);

		return b;
	}
	
	/**Retorna o log das operações executaas nesta conta
	 * @return O log
	 * @see #log*/
	public ArrayDeque<BankOperation> getLog()
	{
		return log;
	}
	
	/**
	 * Retorna uma string contendo o log formatado das operação realizadas nesta conta
	 * @return Uma string contendo o log, formatado amigavelmente.
	 * */
	public String getLogAsString()
	{
		String completeLog = "";
		for(BankOperation op : getLog())
			completeLog += op.toString();
		return completeLog;
	}
	

	/**Retorna o saldo atual desta conta
	 * @return O saldo
	 * @see #balance*/
	public int getBalance() {
		return balance;
	}

	/**Retorna o ID desta conta
	 * @return O ID desta conta
	 * @see #accountID
	 * */
	public int getAccountID() {
		return accountID;
	}

	/**
	 * Realiza o depósito de $<code>amount</code> nesta conta. 
	 * @param amount Quantidade de dinheiro depositada
	 * @return <code>true</code> se o depósito foi bem-sucedido (Geralmente é), <code>false</code> caso contrário
	 */
	private Boolean deposit(int amount) {
		if (amount >= 0 && balance <= Integer.MAX_VALUE - amount) {
			balance += amount;
			logger.info("Deposited: $" + amount + ". Balance: $" + balance);
			return true;
		} else {
			logger.error("Deposit Error: failed to deposit $" + amount + ". Balance: $" + balance);
			return false;
		}
	}

	/**
	 * Realiza o saque de $<code>amount</code> desta conta. 
	 * @param amount Quantidade de dinheiro sacada
	 * @return <code>true</code> se o saque foi bem-sucedido, <code>false</code> caso contrário (Caso o valor desejado seja maior que o {@link #balance saldo}). 
	 */
	private Boolean withdraw(int amount) {
		if (amount >= 0 && balance - amount >= 0) {
			balance -= amount;
			logger.info("Withdrawn: $" + amount + ". Balance: $" + balance); 
			return true;
		} else {
			logger.error("Withdraw Error: failed to withdraw $" + amount + ". Balance: $" + balance);
			return false;
		}
	}
	
	@Override
	public String toString() {
		return "Account #" + accountID + " - Balance: $" + balance;
	}
}
